﻿//
// *************************************************************************************
// WP-Framework
// 
// Developed by
// Lukas Kretschmar (lkretschmar.dev@bluewin.ch)
//
// Switzerland, Copyright (c) 2013 lkretschmar
// This content is released under the MIT License (http://opensource.org/licenses/MIT).
//     
// *************************************************************************************
//
//
			

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using KretschIT.WP_Fx.Core.Validation;
using System.Reflection;

namespace KretschIT.WP_Fx.EasyMoq.Calls.Properties
{
    internal class PropertyMock<T> : IPropertyMock<T>, IPropertySetter<T>
    {
        private MemberInfo property;
        private bool allowOverrideException = true;
        private readonly T obj;

        public PropertyMock(MemberInfo property, T obj)
        {
            It.IsNotNull(() => property);
            It.IsNotNull(() => obj);

            this.property = property;
            this.obj = obj;
        }

        public PropertyMock(MemberExpression property, T obj)
        {
            It.IsNotNull(() => property);
            It.IsNotNull(() => obj);

            this.property = property.Member;
            this.obj = obj;
        }

        private object value;

        public object Value
        {
            get
            {
                this.GetterCalls++;
                return this.GetValue();
            }
            set
            {
                this.SetterCalls++;
                this.SetValue(value);
            }
        }

        #region Getter

        private object GetValue()
        {
            var v = this.value;
            this.ThrowExceptionIfExists(v);
            this.RaiseGetterEventsIfExists();
            return GetReturnValue(v);
        }

        private void ThrowExceptionIfExists(object v)
        {
            if (v is ExceptionAdapter)
            {
                throw ((ExceptionAdapter)v).Exception;
            }
        }

        private void RaiseGetterEventsIfExists()
        {
            if (this.getterEvents.Any() && this.obj is IEventsOf<T>)
            {
                var sender = (IEventsOf<T>) this.obj;
                foreach (var e in this.getterEvents)
                {
                    e.Invoke(sender);
                }
            }
        }
        
        private object GetReturnValue(object v)
        {
            if (this.convert != null)
            {
                return this.convert(v);
            }
            return v;
        }
        
        private Func<object, object> convert;

        public void SetValueConverter(Func<object, object> convert)
        {
            this.convert = convert;
        }

        public bool Is(MemberInfo property)
        {
            if (property == null)
            {
                return false;
            }
            return this.HaveSameName(property) && this.HaveSameDeclaringType(property);
        }

        private bool HaveSameName(MemberInfo property)
        {
            return property.Name == this.property.Name;
        }
        
        private bool HaveSameDeclaringType(MemberInfo property)
        {
            return property.DeclaringType == this.property.DeclaringType;
        }

        public int GetterCalls { get; private set; }

        public void Returns(object value)
        {
            this.value = value;
        }

        public void Throws(Exception exception)
        {
            this.Throws(exception, true);
        }

        public void Throws(Exception exception, bool allowOverrideWithSetter)
        {
            this.value = new ExceptionAdapter(exception);
            this.allowOverrideException = allowOverrideWithSetter;
        }

        private readonly List<Action<IEventsOf<T>>> getterEvents = new List<Action<IEventsOf<T>>>(); 

        public void Raise(Action<IEventsOf<T>> eventCall)
        {
            if (eventCall != null)
            {
                getterEvents.Add(eventCall);
            }            
        }

        #endregion

        #region Setter

        private Exception setterException;

        void IPropertySetter<T>.Throws(Exception exception)
        {
            this.setterException = exception ?? new NullReferenceException();
        }

        private readonly List<Action<IEventsOf<T>>> setterEvents = new List<Action<IEventsOf<T>>>();

        void IPropertySetter<T>.Raise(Action<IEventsOf<T>> eventCall)
        {
            if (eventCall != null)
            {
                this.setterEvents.Add(eventCall);
            }
        }

        private void SetValue(object value)
        {
            this.ThrowSetterExceptionIfExsists();
            if (this.allowOverrideException)
            {
                this.value = value;
            }
            this.RaiseSetterEventsIfExists();
        }

        private void ThrowSetterExceptionIfExsists()
        {
            if (this.setterException != null)
            {
                throw this.setterException;
            }
        }
        
        private void RaiseSetterEventsIfExists()
        {
            if (this.setterEvents.Any() && this.obj is IEventsOf<T>)
            {
                var sender = (IEventsOf<T>)this.obj;
                foreach (var e in this.setterEvents)
                {
                    e.Invoke(sender);
                }
            }
        }

        public int SetterCalls { get; private set; }

        #endregion
    }
}
